package com.example.starter.exception;

import com.google.common.collect.Iterators;

import com.alibaba.fastjson.JSON;
import com.example.starter.dto.ResultBean;
import com.example.starter.properties.CommonProperties;

import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.MessageSourceResolvable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

import java.io.UnsupportedEncodingException;
import java.util.List;
import java.util.stream.Collectors;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Path;

/**
 * 全局异常处理返回json格式有两种途径，返回ResponseEntity或使用@ResponseBody注解
 *
 * 拦截器回默认httpcode=200，可以通过@ResponseStatus制定其他值
 *
 * Created by liuw on 2017/11/14.
 */

@EnableConfigurationProperties({CommonProperties.class})
@AutoConfigureAfter(CommonProperties.class)
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {

    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @Autowired
    private CommonProperties commonProperties;

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ResultBean> exceptionHandler(MethodArgumentNotValidException exception) {

        final List<FieldErrorEntity> collect = exception.getBindingResult().getAllErrors().stream().map(this::mapToFieldErrorEntity).collect(
                Collectors.toList());

        ResultBean result = new ResultBean();
        result.setCode(ResultBean.BEAN_VALIDATION_ERROR);
        //todo 待优化，msg结构特殊，解析复杂
        result.setMsg(JSON.toJSONString(collect));

        return ResponseEntity.ok(result);
    }

    @ExceptionHandler(ConstraintViolationException.class)
    public ResponseEntity<ResultBean> valiateExceptionHandler(ConstraintViolationException exception) {
        final List<?> collect = exception.getConstraintViolations().stream()
                                         .map(this::mapToFieldErrorEntity).collect(Collectors.toList());

        ResultBean result = new ResultBean();
        result.setCode(ResultBean.BEAN_VALIDATION_ERROR);
        //todo 待优化，msg结构特殊，解析复杂
        result.setMsg(JSON.toJSONString(collect));

        return ResponseEntity.ok(result);
    }

    //@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(value = Exception.class)
    public ResultBean defaultErrorHandler(Exception e) {
        String fullStackTrace = ExceptionUtils.getStackTrace(e);
        ResultBean<?> result = new ResultBean();
        logger.error(" error ", e);
        //TODO 未知的异常，应该格外注意，可以发送邮件通知等
        result.setMsg(commonProperties.isStacktrace() ? fullStackTrace : e.getMessage());
        result.setCode(ResultBean.FAIL);

        return result;
    }

    /**
     * Required Integer parameter 'bb' is not present
     * @param e
     * @return
     */
    @ExceptionHandler(value = MissingServletRequestParameterException.class)
    public ResultBean RequestParameterMissException(Exception e) {
        String fullStackTrace = ExceptionUtils.getStackTrace(e);
        ResultBean<?> result = new ResultBean();
        logger.warn(e.getMessage());
        result.setMsg(commonProperties.isStacktrace() ? fullStackTrace : e.getMessage());
        result.setCode(ResultBean.FAIL);

        return result;
    }


    @ExceptionHandler(value = CheckedException.class)
    public ResultBean defaultErrorHandler(CheckedException e) {

        ResultBean<?> result = new ResultBean();

        // 已知异常
        result.setMsg(e.getLocalizedMessage());
        result.setCode(ResultBean.FAIL);

        return result;
    }

    /**
     * 数据绑定对象在controller的aop之外处理；
     * 绑定后validation在aop之内
     * @param exception
     * @return
     */
    @ExceptionHandler(BindException.class)
    public ResultBean<String> exceptionHandler(BindException exception) {
        final List<FieldErrorEntity> collect = exception.getAllErrors().stream().map(this::mapToFieldErrorEntity).collect(
                Collectors.toList());
        ResultBean result = new ResultBean();
        result.setCode(ResultBean.BEAN_VALIDATION_ERROR);
        //todo 待优化，msg结构特殊，解析复杂
        result.setMsg(JSON.toJSONString(collect));

        return result;
    }

    private FieldErrorEntity mapToFieldErrorEntity(ObjectError error) {
        MessageSourceResolvable messageSourceResolvable = (MessageSourceResolvable) error.getArguments()[0];
        return new FieldErrorEntity(messageSourceResolvable.getDefaultMessage(),
                                    toUtf8String(error.getDefaultMessage()));
    }

    private FieldErrorEntity mapToFieldErrorEntity(ConstraintViolation<?> violation) {
        return new FieldErrorEntity(getFieldName(violation),
                                    toUtf8String(violation.getMessage()));
    }

    private String getFieldName(ConstraintViolation<?> violation) {
        Path path = violation.getPropertyPath();
        return Iterators.getLast(path.iterator()).getName();
    }

    private String toUtf8String(String origin) {
        try {
            return new String(origin.getBytes("ISO-8859-1"), "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return origin;
    }
}
